#include "timer.h"
#include "es_print.h"

sigset_t    tt_set; /* signal set for timer thread */
es_timers_t es_timers; /* timers set */

struct itimerval basic_intvl = {
    {ES_TINTVL_S, ES_TINTVL_US},
    {ES_TDELAY_S, ES_TDELAY_US}
};


void es_timer_init()
{
    pthread_mutex_init(&es_timers.lock, NULL);
    pthread_mutex_lock(&es_timers.lock);

    memset(&es_timers.timers, 0, sizeof(es_timers.timers));
    es_timers.timers.tmask = 0xffffffff;

    pthread_mutex_unlock(&es_timers.lock);
}

/* start timers set with setitimer */
int es_timer_start()
{
    int rval;
    rval = setitimer(ITIMER_REAL, &basic_intvl, NULL);
    if(0 == rval){
        fprintf(stdout, "sucessfully start timer \n");
    }else{
        fprintf(stderr, "error in start timer %d[%s]\n", \
                errno, strerror(errno));
    }

    return rval;
}

/* set necessary value, when add a new timer */
void es_timers_add_set(tproc_t *procs, char *name, unsigned int timeout, unsigned int intval, void (* pcb)(void *), void *arg)
{
    unsigned int cnt_to;
    unsigned int cnt_itvl;

    struct  timeval now;

    strcpy(procs->name, name);

    cnt_to = (timeout)/TIMER_BASIC_INTVL;
    if(cnt_to < 1){
        cnt_to = 1;
    }

    cnt_itvl = (intval)/TIMER_BASIC_INTVL;
    if(cnt_itvl < 1){
        cnt_itvl = 1;
    }

    procs->period = cnt_itvl;
    procs->wait = cnt_to;
    procs->pf = pcb;
    procs->para = arg;
    procs->period_t = intval;

    gettimeofday(&now, NULL);
    procs->next_t  = now;
    procs->next_t.tv_sec += procs->period_t;

    procs->repeat = true;   /* default is repeatable, 
                             * if intval==0: repeat will be set false
                             * after scheduled first time
                             */
}

/* add a new timer
 * @name: timer's name
 * @timeout: the first time when the timer timeout
 * @intval: period; if intval == 0, the timer is disposable
 * @pcb: function will be executed
 * @arg: passed to @pcb
 */
int es_timer_add(char* name, unsigned int timeout, unsigned int intval, void (* pcb)(void *), void *arg)
{
    unsigned int i, map;

    if(name == NULL || strlen(name) > TIMER_NAME_SIZE){
        fprintf(stderr, "timer's name '%s' is NULL or too long \n", name);
        return -1;
    }
    
    /* avoid timer_init() is executed after es_timer_add()
     * timer_init() is called by (*es_timer_thread)(void *)
     */
    while((es_timers.flag & (1<<0)) != 1){
        usleep(1000);
    }

    pthread_mutex_lock(&es_timers.lock);

    map = es_timers.timers.tmap;
    for(i = 0; i < TIMER_CNT; i++){
        /* find an unused timer id */
        if( !((1<<i)&map) ){

            es_timers_add_set(&es_timers.timers.procs[i], name, timeout, intval, pcb, arg);

            ES_PRT_INFO("add timer '%s' successfully, id = %d, to=%d tv=%d \n", \
                            es_timers.timers.procs[i].name, i, timeout, intval);

            es_timers.timers.tmap |= (unsigned int)(1<<i);
            es_timers.timers.tmask |= (unsigned int)(1<<i);

            break;

        }else if(strcmp(es_timers.timers.procs[i].name, name) == 0){
            ES_PRT_ERROR("duplicate name %s, when add timer \n", name);
            return -1;
        }
    }
    
    if(i == TIMER_CNT){
        ES_PRT_ERROR("add timer '%s' failed: num reach the upper limit \n", name);
        return -1;
    }

    pthread_mutex_unlock(&es_timers.lock);
    return i;
}

/*set next schedule time*/
void es_timers_nextsch_set(tproc_t *procs, unsigned int period_t)
{
    struct timeval now;
    gettimeofday(&now, NULL);

    procs->wait = (period_t/TIMER_BASIC_INTVL);
    procs->next_t = now;
    procs->next_t.tv_sec += period_t;
}

/* find out which timer is time out, then execute its callback function */
void es_timer_sche()
{
    unsigned int i;
    unsigned int map;
    struct timeval now;

    pthread_mutex_lock(&es_timers.lock);
    /* tmask is initailized with 0xffffffff
     * if one timer is used, the corresponding bit is set 1 in tmap
     */
    map = es_timers.timers.tmap & es_timers.timers.tmask;

    for(i = 0; i < TIMER_CNT; i++){
        /* for each all existing timers */
        /* if the timer is unactive, map's bit==0*/
        if (!((1<<i)&map))
        continue;

        /* time out, wait downs to 0 */
        if (es_timers.timers.procs[i].wait > 0) {
            es_timers.timers.procs[i].wait -= 1;
        }else {
            es_timers.timers.procs[i].wait = 0;
        }

        if (es_timers.timers.procs[i].wait <= 0) {
            /* if timeout, now should later than next_t, which means now > next_t */
            gettimeofday(&now, NULL);
            if (TM_NOW_GT_NEXT(es_timers.timers.procs[i].next_t, now)) {

                /* periodic timer can be scheduled repeatedly
                 * disposable timer only can be scheduled once
                 */
                if(es_timers.timers.procs[i].repeat == true){

                    /* periodic timer: reset next sched time  */
                    es_timers_nextsch_set(&es_timers.timers.procs[i], es_timers.timers.procs[i].period_t);
                    /*  .pf can overwrite periodic time by calling es_timers_nextsch_set()*/
                    (*es_timers.timers.procs[i].pf)(es_timers.timers.procs[i].para);

                    if(es_timers.timers.procs[i].period_t > 0){
                        es_timers.timers.procs[i].repeat = true;
                    }else{
                        es_timers.timers.procs[i].repeat = false;
                    }

                }

            }else {
                /* debug info, remove if no needed */
            }
        }
    }

    pthread_mutex_unlock(&es_timers.lock);
}

/* del timer by name */
int es_timer_del(char *name)
{
    int tid;
    /* find timer by name */
    for(tid=0; tid<TIMER_CNT; tid++){
        if(strcmp(es_timers.timers.procs[tid].name, name) == 0){
            break;
        }
    }
    if(tid<0 || tid==TIMER_CNT){
        ES_PRT_WARN("timer: %s unexist \n", name);
        return tid;
    }

    pthread_mutex_lock(&es_timers.lock);
    ES_PRT_INFO("Delete timer id = %d, name = %s \n", tid, es_timers.timers.procs[tid].name);

    memset(&es_timers.timers.procs[tid], 0, sizeof(es_timers.timers.procs[tid]));
    es_timers.timers.tmap &= ~(1<<tid);
    es_timers.timers.tmask |= (1<<tid);//reset

    pthread_mutex_unlock(&es_timers.lock);

    return tid;
}

int es_timer_pause(int tid)
{
    int bit = 0x00000001<<tid;

    pthread_mutex_lock(&es_timers.lock);
    if(bit & es_timers.timers.tmap){
        es_timers.timers.tmask &= ~bit;
        bit = tid;
    }else{
        bit = -1;
    }
    pthread_mutex_unlock(&es_timers.lock);

    return bit;
}

/* restart timer by tid, you can restruct code to restart timer by name */
int es_timer_restart(int tid, int interval)
{
    int bit = 0x00000001<<tid;

    pthread_mutex_lock(&es_timers.lock);
    es_timers.timers.tmask |= bit;
    if(bit & es_timers.timers.tmap){
        /* the next time out is interval seconds */
        es_timers_nextsch_set(&es_timers.timers.procs[tid], interval);
        bit = tid;
    }else{
        bit = -1;
    }
    pthread_mutex_unlock(&es_timers.lock);

    return bit;
}

void *es_timer_thread(void *arg)
{
    sigset_t *set = (sigset_t*)arg;
    pthread_detach(pthread_self());

    int sig;
    int rval;
    int stop=0;

    pthread_mutex_lock(&es_timers.lock);
    es_timer_init();
    es_timer_start();
    es_timers.flag |= (1<<0); /* init finished */
    pthread_mutex_unlock(&es_timers.lock);

    while(stop==0){
        /* every TIMER_BASIC_INTVL, ALARM signal was generated */
        rval = sigwait(set, &sig);
        if(rval != 0){
            fprintf(stderr, "error occurs in timer thread, no:%d, meaning:%s\n", \
                    errno, strerror(errno));
            rval = 1;
            stop = 1;
            continue;
        }
        es_timer_sche();
    }

    sleep(1);
    pthread_exit((void *)&rval);

}